// A lot of the code here is based on code from
// https://github.com/plasma-umass/browsix-emscripten

import { Buffer } from './buffer';

import * as marshal from './marshal';
import * as util from './util'

import { ENOENT, O_APPEND, O_CREAT, O_EXCL, O_RDONLY, O_RDWR, O_SYNC, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, WASI_EMLINK } from "./constants";
import { SYS, syscall } from "./syscall";
import path from './path';


interface IFStatOptions {
    bigint?: boolean;
}

const searchParams = Symbol('query');
const realpathCacheKey = Symbol('realpathCacheKey')
const emptyObj = Object.create(null);

const ReflectApply = Reflect.apply;

function statsFromValues(shmBuf) {

    let stats = new Stats();
    let view = new DataView(shmBuf.buffer, shmBuf.byteOffset);

    // @ts-ignore
    const [len, err] = marshal.Unmarshal(stats, view, 0, marshal.fs.StatDef);

    if (err) {
        throw new Error("fstat() Failed: " + err);
    }

    // this isn't stored in a standard stat() response -- solve it
    // the same way upstream node does:
    // https://github.com/nodejs/node/issues/2222
    stats._updateBirthtime();

    return stats
}

function encodeRealpathResult(result, options) {
    if (!options || !options.encoding || options.encoding === 'utf8')
        return result;
    const asBuffer = Buffer.from(result);
    if (options.encoding === 'buffer') {
        return asBuffer;
    } else {
        return asBuffer.toString(options.encoding);
    }
}

function nextPart(p, i) { return p.indexOf('/', i); }
function splitRoot(str) {
    for (var i = 0; i < str.length; ++i) {
        if (str.charCodeAt(i) !== 47/*'/'*/)
            return str.slice(0, i);
    }
    return str;
};

function nullCheck(path, callback?) {
    if (('' + path).indexOf('\u0000') !== -1) {
        var er = new Error('Path must be a string without null bytes');
        // @ts-ignore
        er.code = 'ENOENT';
        if (typeof callback !== 'function')
            throw er;
        //process.nextTick(callback, er);
        setTimeout(callback, 0)
        return false;
    }
    return true;
}

function handleError(val, callback?) {
    if (val instanceof Error) {
        if (typeof callback === 'function') {
            //process.nextTick(callback, val);
            setTimeout(callback, 0)
            return true;
        } else throw val;
    }
    return false;
}

function getPathFromURLPosix(url) {
    if (url.hostname !== '') {
        return throwTypeError(`ERR_INVALID_FILE_URL_HOST must be "localhost" or empty on brower linux`);
    }
    var pathname = url.pathname;
    for (var n = 0; n < pathname.length; n++) {
        if (pathname[n] === '%') {
            var third = pathname.codePointAt(n + 2) | 0x20;
            if (pathname[n + 1] === '2' && third === 102) {
                return throwTypeError('ERR_INVALID_FILE_URL_PATH must not include encoded / characters');
            }
        }
    }
    return decodeURIComponent(pathname);
}

function getPathFromURL(path) {
    if (path == null || !path[searchParams] ||
        !path[searchParams][searchParams]) {
        return path;
    }
    if (path.protocol !== 'file:')
        return throwTypeError('ERR_INVALID_URL_SCHEME file');
    return getPathFromURLPosix(path);
}

function assertEncoding(encoding) {
    if (encoding && !Buffer.isEncoding(encoding)) {
        throw new Error(`Unknown encoding: ${encoding}`);
    }
}

function getOptions(options, defaultOptions) {
    if (options === null || options === undefined ||
        typeof options === 'function') {
        return defaultOptions;
    }

    if (typeof options === 'string') {
        defaultOptions = util._extend({}, defaultOptions);
        defaultOptions.encoding = options;
        options = defaultOptions;
    } else if (typeof options !== 'object') {
        throw new TypeError('"options" must be a string or an object, got ' +
            typeof options + ' instead.');
    }

    if (options.encoding !== 'buffer')
        assertEncoding(options.encoding);
    return options;
}

function throwTypeError(errmsg) {
    throw new TypeError(errmsg);
}

// converts Date or number to a fractional UNIX timestamp
function toUnixTimestamp(time) {
    // eslint-disable-next-line eqeqeq
    // @ts-ignore
    if (typeof time === 'string' && +time == time) {
        return +time;
    }
    if (Number.isFinite(time)) {
        if (time < 0) {
            return Date.now() / 1000;
        }
        return time;
    }
    if (util.isDate(time)) {
        // convert to 123.456 UNIX timestamp
        return time.getTime() / 1000;
    }
    throw new Error('Cannot parse time: ' + time);
}

function uncurryThis(func) {
    return (thisArg, ...args) => ReflectApply(func, thisArg, args);
}

const TypedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);

const TypedArrayProto_toStringTag =
    uncurryThis(
        Object.getOwnPropertyDescriptor(TypedArrayPrototype,
            Symbol.toStringTag).get);

function isUint8Array(value) {
    return TypedArrayProto_toStringTag(value) === 'Uint8Array';
}

// const statValues = getStatValues();

// function statsFromValues() {
//   return new Stats(statValues[0], statValues[1], statValues[2], statValues[3],
//                    statValues[4], statValues[5],
//                    statValues[6] < 0 ? undefined : statValues[6], statValues[7],
//                    statValues[8], statValues[9] < 0 ? undefined : statValues[9],
//                    statValues[10], statValues[11], statValues[12],
//                    statValues[13]);
// }


function modeToNumber(m, def?) {
    if (typeof m === 'number')
        return m;
    if (typeof m === 'string')
        return parseInt(m, 8);
    if (def)
        return modeToNumber(def);
    return undefined;
}

function flagsToNumber(flag) {
    if (typeof flag === 'number') {
        return flag;
    }

    switch (flag) {
        case 'r': return O_RDONLY;
        case 'rs': // Fall through.
        case 'sr': return O_RDONLY | O_SYNC;
        case 'r+': return O_RDWR;
        case 'rs+': // Fall through.
        case 'sr+': return O_RDWR | O_SYNC;

        case 'w': return O_TRUNC | O_CREAT | O_WRONLY;
        case 'wx': // Fall through.
        case 'xw': return O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;

        case 'w+': return O_TRUNC | O_CREAT | O_RDWR;
        case 'wx+': // Fall through.
        case 'xw+': return O_TRUNC | O_CREAT | O_RDWR | O_EXCL;

        case 'a': return O_APPEND | O_CREAT | O_WRONLY;
        case 'ax': // Fall through.
        case 'xa': return O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
        case 'as': // Fall through.
        case 'sa': return O_APPEND | O_CREAT | O_WRONLY | O_SYNC;

        case 'a+': return O_APPEND | O_CREAT | O_RDWR;
        case 'ax+': // Fall through.
        case 'xa+': return O_APPEND | O_CREAT | O_RDWR | O_EXCL;
        case 'as+': // Fall through.
        case 'sa+': return O_APPEND | O_CREAT | O_RDWR | O_SYNC;
    }

    throw new Error('Unknown file open flag: ' + flag);
}

class FSReqWrap {
    oncomplete: (err: any, ...rest: any[]) => void = undefined;
    context: any = undefined;

    constructor() {
    }

    complete(...args: any[]): void {
        this.oncomplete.apply(this, arguments);
    }
}

// FIXME: this is copied from node's stdlib in order to avoid
// dependency/initialization issues
class Stats {
    dev: number;
    mode: number;
    nlink: number;
    uid: number;
    gid: number;
    rdev: number;
    blksize: number;
    ino: number;
    size: number;
    blocks: number;
    atime: Date;
    mtime: Date;
    ctime: Date;
    birthtime: Date;

    constructor() {
    }

    _updateBirthtime(): void {
        let oldest = this.atime;
        if (this.mtime < oldest)
            oldest = this.mtime;
        if (this.ctime < oldest)
            oldest = this.ctime;
    }

    _checkModeProperty(property: any): boolean {
        return ((this.mode & S_IFMT) === property);
    }

    isDirectory(): boolean {
        return this._checkModeProperty(S_IFDIR);
    }

    isFile(): boolean {
        return this._checkModeProperty(S_IFREG);
    }

    isBlockDevice(): boolean {
        return this._checkModeProperty(S_IFBLK);
    }

    isCharacterDevice(): boolean {
        return this._checkModeProperty(S_IFCHR);
    }

    isSymbolicLink(): boolean {
        return this._checkModeProperty(S_IFLNK);
    }

    isFIFO(): boolean {
        return this._checkModeProperty(S_IFIFO);
    }

    isSocket(): boolean {
        return this._checkModeProperty(S_IFSOCK);
    }
}

function statFinished(req: FSReqWrap, err: any, buf: Uint8Array): void {
    if (err) {
        // FIXME: we should have a more general way of
        // converting back from numerical to Node errors
        if (typeof err === 'number') {
            let nerr = err;
            err = new Error('FS error');
            if (nerr === -ENOENT) {
                err.code = 'ENOENT';
            }
        }
        req.complete(err, null);
        return;
    }

    let stats = new Stats();
    let view = new DataView(buf.buffer, buf.byteOffset);
    let len: number;
    [len, err] = marshal.Unmarshal(stats, view, 0, marshal.fs.StatDef);

    // this isn't stored in a standard stat() response -- solve it
    // the same way upstream node does:
    // https://github.com/nodejs/node/issues/2222
    stats._updateBirthtime();

    req.complete(err, stats);
}

function open(path: string, flags: string, mode: number, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "open", [path, flags, mode])
}

function unlink(path: string, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "unlink", [path])
}

function rmdir(path: string, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "rmdir", [path])
}

function mkdir(path: string, mode: number, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "mkdir", [path, mode])
}

function utimes(path: string, atime: number, mtime: number, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "utimes", [path, atime, mtime])
}

function futimes(fd: number, atime: number, mtime: number, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "futimes", [fd, atime, mtime])
}

function readdir(path: string, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "readdir", [path])
}

function fstat(fd: number, req: FSReqWrap): void {
    syscall.async(statFinished.bind(undefined, req), "fstat", [fd])
}

function lstat(path: string, req: FSReqWrap): void {
    syscall.async(statFinished.bind(undefined, req), "lstat", [path])
}

function stat(path: string, req: FSReqWrap): void {
    syscall.async(statFinished.bind(undefined, req), "stat", [path])
}

function read(fd: number, buffer: any, offset: number, len: number, pos: number, req: FSReqWrap): void {
    if (typeof pos === 'undefined')
        pos = -1;

    syscall.async(function readFinished(err: any, lenRead: number, data: Uint8Array): void {
        if (err) {
            req.complete(err, null);
            return;
        }
        for (let i = 0; i < lenRead; i++)
            buffer.writeUInt8(data[i], offset + i);
        req.complete(null, lenRead);
    }, "pread", [fd, len, pos])
}

function close(fd: number, req: FSReqWrap): void {
    syscall.async(req.complete.bind(req), "close", [fd])
}

// sync

function writeBuffer(fd: number, buffer: any, offset: number, len: number, pos: number): number {

    let ptr = offset;
    let ret = 0;

    // it is possible for the buffer being written to be larger
    // than our shared memory segment.  In the common case this
    // while loop executes once, but for large source buffers
    // will iterate several times.
    while (len > 0) {
        let shmBuf = syscall.getShm(len);

        syscall.copyFromUser(shmBuf, ptr, buffer);

        var written = syscall.sync(SYS.write, fd, syscall.SHM_OFF, shmBuf.length);
        if (written < 0)
            return ret === 0 ? written : ret;
        ret += written;

        ptr += shmBuf.length;
        len -= shmBuf.length;

        return ret;
    }
}

function writeString(fd: number, string: any, offset: number, len: number, pos: number): number {
    const buffer = Buffer.from(string);
    return syscall.sync(SYS.PWRITE64, fd, buffer.slice(offset, offset + len), pos)
}

// FIXME: be efficient
// function writeBuffers(fd: number, chunks: any, pos: number, req: FSReqWrap): void {
//     let errored = false;
//     let done = 0;
//     let written = 0;
//     function chunkComplete(err: any, count: number): void {
//         done++;
//         if (err && !errored) {
//             req.complete(err, undefined);
//             errored = true;
//             return;
//         }
//         written += count;
//         if (done === chunks.length)
//             req.complete(null, written);
//     }
//     for (let i = 0; i < chunks.length; i++) {
//         let chunkReq = new FSReqWrap();
//         chunkReq.oncomplete = chunkComplete;
//         writeBuffer(fd, chunks[i], 0, chunks[i].length, undefined, chunkReq);
//     }
// }
function writeSync(fd: number, buffer: any, offset: number, length: number, position: number): number {
    if (isUint8Array(buffer)) {
        if (position === undefined)
            position = null;
        if (typeof offset !== 'number')
            offset = 0;
        if (typeof length !== 'number')
            length = buffer.length - offset;
        return writeBuffer(fd, buffer, offset, length, position);
    }
    if (typeof buffer !== 'string')
        buffer += '';
    if (offset === undefined)
        offset = null;
    return writeString(fd, buffer, offset, length, position);
}

function fstatSync(fd: number, options?: any): Stats {

    const size = marshal.fs.StatDef.length;
    const off = syscall.SHM_OFF
    // 获取一段内存
    const shmBuf = syscall.getShmAt(off, size);

    syscall.sync(SYS.FSTAT64, fd, off);

    let stats = new Stats();

    let view = new DataView(shmBuf.buffer, shmBuf.byteOffset);

    // @ts-ignore
    const [len, err] = marshal.Unmarshal(stats, view, 0, marshal.fs.StatDef);

    if (err) {
        throw new Error("fstat() Failed: " + err);
    }

    // this isn't stored in a standard stat() response -- solve it
    // the same way upstream node does:
    // https://github.com/nodejs/node/issues/2222
    stats._updateBirthtime();

    return stats
}

function readSync(fd: number, buffer: Buffer | Uint8Array, offset: number, length: number, position?: number): number {
    // TODO:
    if (typeof position === 'undefined')
        position = -1;
    if (length === 0) {
        return 0;
    }

    const off = syscall.SHM_OFF
    const size = length;

    const shmBuf = syscall.getShmAt(off, size);

    console.log(shmBuf)

    const len = syscall.sync(SYS.READ, fd, off, size)

    if (len < 0) {
        throw new Error("read() Failed: " + len);
    }

    syscall.copyToUser(off, size, buffer)

    return len
}

function readdirSync(path: any, options?: any): string[] {
    options = getOptions(options, {});
    handleError((path = getPathFromURL(path)));
    nullCheck(path);
    //return binding.readdir(pathModule._makeLong(path), options.encoding);
    const pathOff = syscall.SHM_OFF
    const bufOff = syscall.putShmString(path._makeLong(path))

    const ret = syscall.sync(SYS.READDIR, pathOff)

    if (ret < 0) {
        throwTypeError('Invalid character encoding for filename')
    }

    return syscall.getStringArrayAt(bufOff)
}

function lstatSync(path): Stats {
    handleError((path = getPathFromURL(path)));
    nullCheck(path);

    const pathOff = syscall.SHM_OFF
    const bufOff = syscall.putShmString(path)
    const size = marshal.fs.StatDef.length;

    const shmBuf = syscall.getShmAt(bufOff, size);

    // binding.lstat(path._makeLong(path));
    syscall.sync(SYS.LSTAT, pathOff, bufOff)

    return statsFromValues(shmBuf);
}

function unlinkSync(path: any): void {
    handleError((path = getPathFromURL(path)));
    nullCheck(path);
    const pathOff = syscall.SHM_OFF
    syscall.putShmString(path._makeLong(path))
    //return binding.unlink(pathModule._makeLong(path));
    return syscall.sync(SYS.UNLINK, pathOff)

}

function symlinkSync(target: any, path: any, type?: any): void {
    type = (typeof type === 'string' ? type : null);
    handleError((target = getPathFromURL(target)));
    handleError((path = getPathFromURL(path)));
    nullCheck(target);
    nullCheck(path);

    const targetOff = syscall.SHM_OFF
    const pathOff = syscall.putShmString(target)

    syscall.putShmString(pathOff, pathOff)
    //return binding.symlink(preprocessSymlinkDestination(target, type, path),pathModule._makeLong(path),type);
    return syscall.sync(SYS.SYMLINK, targetOff, pathOff)
}

function renameSync(oldPath: any, newPath: any): void {
    handleError((oldPath = getPathFromURL(oldPath)));
    handleError((newPath = getPathFromURL(newPath)));
    nullCheck(oldPath);
    nullCheck(newPath);

    const oldPathOff = syscall.SHM_OFF
    const newPathOff = syscall.putShmString(path._makeLong(oldPath))
    syscall.putShmString(path._makeLong(newPath), newPathOff)

    //return binding.rename(pathModule._makeLong(oldPath),pathModule._makeLong(newPath));
    return syscall.sync(SYS.RENAME, oldPathOff, newPathOff)
}

function rmdirSync(path, options?): void {
    handleError((path = getPathFromURL(path)));
    nullCheck(path);

    const pathOff = syscall.SHM_OFF
    syscall.putShmString(path._makeLong(path))

    //return binding.rmdir(pathModule._makeLong(path));
    return syscall.sync(SYS.RMDIR, pathOff)
}

function readlinkSync(path: any, options?: any): string | Buffer {
    options = getOptions(options, {});
    handleError((path = getPathFromURL(path)));
    nullCheck(path);

    const pathOff = syscall.SHM_OFF
    const bufOff = syscall.putShmString(path._makeLong(path))
    //return binding.readlink(path._makeLong(path), options.encoding);
    const ret = syscall.sync(SYS.READLINK, pathOff)

    if (ret < 0) {
        throwTypeError('Invalid character encoding for link')
    }

    //const buf = Buffer.alloc(ret)
    //let shmBuf = syscall.getShm(ret);
    //syscall.copyFromUser(shmBuf, bufOff, buf);

    return syscall.getStringAt(bufOff)

}

function openSync(path, flags, mode): number {
    //debugger
    const modeNum = modeToNumber(mode, 0o666);
    //handleError((path = getPathFromURL(path)));
    //nullCheck(path);
    const flagsNum = flagsToNumber(flags);
    syscall.putShmString(path)
    const fd = syscall.sync(SYS.OPEN, syscall.SHM_OFF, flagsNum, modeNum);

    if (fd < 0) {
        throw new Error("open() Failed: " + fd);
    }
    // TODO: fd = -2
    return fd
}

function statSync(path: any, options?: any): Stats {

    handleError((path = getPathFromURL(path)));
    nullCheck(path);

    const pathOff = syscall.SHM_OFF;
    const bufOff = syscall.putShmString(path);

    const size = marshal.fs.StatDef.length;
    const shmBuf = syscall.getShmAt(bufOff, size);

    //binding.stat(pathModule._makeLong(path));
    syscall.sync(SYS.STAT64, pathOff, bufOff);

    return statsFromValues(shmBuf);
}

// 将路径名转换为绝对路径名
function realpathSync(p: any, options?: any): void {

    const statValues = statSync(p)

    if (!options)
        options = emptyObj;
    else
        options = getOptions(options, emptyObj);
    if (typeof p !== 'string') {
        handleError((p = getPathFromURL(p)));
        if (typeof p !== 'string')
            p += '';
    }
    nullCheck(p);
    p = path.resolve(p);

    const cache = options[realpathCacheKey];
    const maybeCachedResult = cache && cache.get(p);
    if (maybeCachedResult) {
        return maybeCachedResult;
    }

    const seenLinks = Object.create(null);
    const knownHard = Object.create(null);
    const original = p;

    // current character position in p
    var pos;
    // the partial path so far, including a trailing slash if any
    var current;
    // the partial path without a trailing slash (except when pointing at a root)
    var base;
    // the partial path scanned in the previous round, with slash
    var previous;

    // Skip over roots
    current = base = splitRoot(p);
    pos = current.length;

    // walk down the path, swapping out linked path parts for their real
    // values
    // NB: p.length changes.
    while (pos < p.length) {
        // find the next part
        var result = nextPart(p, pos);
        previous = current;
        if (result === -1) {
            var last = p.slice(pos);
            current += last;
            base = previous + last;
            pos = p.length;
        } else {
            current += p.slice(pos, result + 1);
            base = previous + p.slice(pos, result);
            pos = result + 1;
        }

        // continue if not a symlink, break if a pipe/socket
        if (knownHard[base] || (cache && cache.get(base) === base)) {
            if ((statValues[1/*mode*/] & S_IFMT) === S_IFIFO ||
                (statValues[1/*mode*/] & S_IFMT) === S_IFSOCK) {
                break;
            }
            continue;
        }

        var resolvedLink;
        var maybeCachedResolved = cache && cache.get(base);
        if (maybeCachedResolved) {
            resolvedLink = maybeCachedResolved;
        } else {
            // Use stats array directly to avoid creating an fs.Stats instance just
            // for our internal use.

            var baseLong = path._makeLong(base);
            lstatSync(baseLong);

            if ((statValues[1/*mode*/] & S_IFMT) !== S_IFLNK) {
                knownHard[base] = true;
                if (cache) cache.set(base, base);
                continue;
            }

            // read the link if it wasn't read before
            // dev/ino always return 0 on windows, so skip the check.
            var linkTarget = null;
            var id;

            var dev = statValues[0/*dev*/].toString(32);
            var ino = statValues[7/*ino*/].toString(32);
            id = `${dev}:${ino}`;
            if (seenLinks[id]) {
                linkTarget = seenLinks[id];
            }

            if (linkTarget === null) {
                statSync(baseLong);
                linkTarget = readlinkSync(baseLong);
            }
            resolvedLink = path.resolve(previous, linkTarget);

            if (cache) cache.set(base, resolvedLink);
            seenLinks[id] = linkTarget;
        }

        // resolve the link, then start over
        p = path.resolve(resolvedLink, p.slice(pos));

        // Skip over roots
        current = base = splitRoot(p);
        pos = current.length;

    }

    if (cache) cache.set(original, p);
    return encodeRealpathResult(p, options);
}

function linkSync(existingPath: any, newPath: any): void {
    //binding.link(pathModule._makeLong(existingPath),pathModule._makeLong(newPath))
    if (!existingPath) {
        throwTypeError("src path required");
    }

    if (!newPath) {
        throwTypeError("dest path required");
    }

    // TODO
    const src_off = syscall.SHM_OFF;
    const dst_off = syscall.putShmString(existingPath);

    syscall.putShmString(newPath, dst_off);

    // uv__fs_fsync -> link
    syscall.sync(SYS.LINK, src_off, dst_off)
}

function utimesSync(path: any, atime: number, mtime: number): void {

    syscall.putShmString(path);

    const atimeTimestamp = toUnixTimestamp(atime);
    const mtimeTimestamp = toUnixTimestamp(mtime);

    //binding.utimes(pathModule._makeLong(path), atime, mtime);
    syscall.sync(SYS.UTIMES, syscall.SHM_OFF, atimeTimestamp, mtimeTimestamp)
}

function mkdirSync(path: any, options?: any): void {

    syscall.putShmString(path);

    const modeNum = modeToNumber(options.mode, 0o777);

    return syscall.sync(SYS.MKDIR, syscall.SHM_OFF, modeNum);
}

function fsyncSync(fd: number): void {
    // return binding.fsync(fd)

    if (!fd) {
        throwTypeError("fd is required")
    }

    if (!Number.isInteger(fd)) {
        throwTypeError("fd must be a file descriptor")
    }

    // uv__fs_fsync -> fsync(req->file)
    syscall.sync(SYS.FSYNC, fd)
}

function closeSync(fd: number): void {
    syscall.sync(SYS.CLOSE, fd);
}

export default {
    open,
    unlink,
    rmdir,
    mkdir,
    utimes,
    futimes,
    readdir,
    fstat,
    lstat,
    stat,
    read,
    close,
    // sync
    writeSync,
    fstatSync,
    readSync,
    readdirSync,
    lstatSync,
    unlinkSync,
    symlinkSync,
    renameSync,
    rmdirSync,
    readlinkSync,
    openSync,
    statSync,
    realpathSync,
    linkSync,
    utimesSync,
    mkdirSync,
    fsyncSync,
    closeSync
}

// TODO:
// @ts-ignore
export function readFileSync(path: string): any {
    debugger
    // XXX better error handling
    console.log("Reading", path);
    // @ts-ignore
    let fd = openSync(path, 0, 0);

    const stat = fstatSync(fd);

    const bytes = new Uint8Array(stat.size);

    readSync(fd, bytes, 0, stat.size);

    console.log("Read", stat.size, "byte file");
    return bytes.buffer;
}

// async function readFile(path: string): Promise<Uint8Array> {
//     // XXX better error handling
//     console.log("Reading", path);
//     let [err, fd] = await open(path, 0, 0);
//     if (err !== 0) {
//         throw new Error("open() Failed: " + err);
//     }
//     let stat_buf;
//     [err, stat_buf] = await fstat(fd);
//     if (err !== 0) {
//         throw new Error("fstat() Failed: " + err);
//     }

//     // TODO don't hardcode offset
//     const st_size = (new BigInt64Array(stat_buf.buffer.slice(48, 48 + 8)))[0];

//     let bytes;
//     let len;
//     [err, len, bytes] = await read(fd, Number(st_size));
//     if (err !== 0) {
//         throw new Error("read() Failed: " + err);
//     }
//     console.log("Read", len, "byte file");
//     return bytes;
// }